import { $t } from '@/locales';
import { useAppStore } from '@/store/modules/app';
import { isNull } from '@/utils/common';
import { useBoolean, useHookTable } from '@sa/hooks';
import { jsonClone } from '@sa/utils';
import type { PaginationProps } from 'naive-ui';
import type { Ref } from 'vue';
import { computed, effectScope, onScopeDispose, reactive, ref, watch } from 'vue';

type TableData = NaiveUI.TableData;
type GetTableData<A extends NaiveUI.TableApiFn> = NaiveUI.GetTableData<A>;
type TableColumn<T> = NaiveUI.TableColumn<T>;

export function useTable<A extends NaiveUI.TableApiFn>(config: NaiveUI.NaiveTableConfig<A>) {
    const scope = effectScope();
    const appStore = useAppStore();

    const isMobile = computed(() => appStore.isMobile);

    const tableColumns = ref(config.columns());

    const { tableKey, apiFn, summaryFn, apiParams, immediate, showTotal } = config;

    const SELECTION_KEY = '__selection__';

    const EXPAND_KEY = '__expand__';

    const {
        loading,
        empty,
        data,
        columns,
        columnChecks,
        reloadColumns,
        getData,
        searchParams,
        updateSearchParams,
        resetSearchParams,
        summaryColumns
    } = useHookTable<A, GetTableData<A>, TableColumn<NaiveUI.TableDataWithIndex<GetTableData<A>>>>({
        tableKey,
        apiFn,
        apiParams,
        summaryFn: summaryFn,
        columns: () => tableColumns.value,
        summaryColumns: config.summaryColumns,
        transformer: (res) => {
            const {
                items = [],
                pageNumber: current = 0,
                pageSize: size = apiParams!.page!.pageSize || 10,
                total = 0,
                totalPage = 0
            } = res.data || {};

            // Ensure that the size is greater than 0, If it is less than 0, it will cause paging calculation errors.
            const pageSize = size <= 0 ? 10 : size;

            const recordsWithIndex = items.map((item, index) => {
                return {
                    ...item,
                    index: (current - 1) * pageSize + index + 1
                };
            });

            return {
                items: recordsWithIndex,
                pageNumber: Number(current),
                pageSize: Number(pageSize),
                total: Number(total),
                totalPage: Number(totalPage)
            };
        },
        getColumnChecks: (cols) => {
            const checks: NaiveUI.TableColumnCheck[] = [];
            cols.filter(
                (column: any) =>
                    isNull(column.visible) || (!isNull(column.visible) && column.visible)
            ).forEach((column) => {
                if (isTableColumnHasKey(column)) {
                    checks.push({
                        key: column.key as string,
                        title: column.title as string,
                        checked: true
                    });
                } else if (column.type === 'selection') {
                    checks.push({
                        key: SELECTION_KEY,
                        title: $t('common.check'),
                        checked: true
                    });
                } else if (column.type === 'expand') {
                    checks.push({
                        key: EXPAND_KEY,
                        title: $t('common.expandColumn'),
                        checked: true
                    });
                }
            });

            return checks;
        },
        getColumns: (cols, checks) => {
            const columnMap = new Map<string, TableColumn<GetTableData<A>>>();

            cols.forEach((column) => {
                if (isTableColumnHasKey(column)) {
                    columnMap.set(column.key as string, column);
                } else if (column.type === 'selection') {
                    columnMap.set(SELECTION_KEY, column);
                } else if (column.type === 'expand') {
                    columnMap.set(EXPAND_KEY, column);
                }
            });

            return checks
                .filter((item) => isNull(item.visible) || item.visible)
                .filter((item) => item.checked)
                .map((check) => columnMap.get(check.key) as TableColumn<GetTableData<A>>);
        },
        onFetched: async (transformed) => {
            const { pageNumber, pageSize, total } = transformed;

            updatePagination({
                page: pageNumber,
                pageSize,
                itemCount: total
            });
        },
        immediate
    });

    const pagination: PaginationProps = reactive({
        page: 1,
        pageSize: 10,
        showSizePicker: true,
        pageSizes: [10, 15, 20, 25, 30],
        onUpdatePage: async (page: number) => {
            pagination.page = page;
            const updateParams = {
                page: {
                    pageNumber: page,
                    pageSize: pagination.pageSize!
                }
            };

            updateSearchParams(updateParams);

            getData();
        },
        onUpdatePageSize: async (pageSize: number) => {
            pagination.pageSize = pageSize;
            pagination.page = 1;

            updateSearchParams({
                page: {
                    pageNumber: pagination.page,
                    pageSize
                }
            });

            getData();
        },
        ...(showTotal
            ? {
                  prefix: (page) => $t('datatable.itemCount', { total: page.itemCount })
              }
            : {})
    });

    // this is for mobile, if the system does not support mobile, you can use `pagination` directly
    const mobilePagination = computed(() => {
        const p: PaginationProps = {
            ...pagination,
            pageSlot: isMobile.value ? 3 : 9,
            prefix: !isMobile.value && showTotal ? pagination.prefix : undefined
        };

        return p;
    });

    function updatePagination(update: Partial<PaginationProps>) {
        Object.assign(pagination, update);
    }

    /**
     * get data by page number
     *
     * @param pageNum the page number. default is 1
     */
    async function getDataByPage(pageNum: number = 1) {
        updatePagination({
            page: pageNum
        });

        updateSearchParams({
            page: {
                pageNumber: pageNum,
                pageSize: pagination.pageSize!
            }
        });

        await getData();
    }

    scope.run(() => {
        watch(
            () => appStore.locale,
            () => {
                reloadColumns();
            }
        );
    });

    onScopeDispose(() => {
        scope.stop();
    });

    return {
        loading,
        empty,
        data,
        columns,
        columnChecks,
        reloadColumns,
        pagination,
        mobilePagination,
        updatePagination,
        summaryColumns: () => summaryColumns.value,
        getData,
        getDataByPage,
        searchParams,
        updateSearchParams,
        resetSearchParams
    };
}

export function useTableOperate<T extends TableData = TableData>(
    data: Ref<T[]>,
    getData: () => Promise<void>
) {
    const { bool: drawerVisible, setTrue: openDrawer, setFalse: closeDrawer } = useBoolean();

    const operateType = ref<NaiveUI.TableOperateType>('add');

    function handleAdd() {
        operateType.value = 'add';
        openDrawer();
    }

    /** the editing row data */
    const editingData: Ref<T | null> = ref(null);

    function handleEdit(id: T['id']) {
        operateType.value = 'edit';
        const findItem = data.value.find((item) => item.id === id) || null;
        editingData.value = jsonClone(findItem);

        openDrawer();
    }

    /** the checked row keys of table */
    const checkedRowKeys = ref<string[]>([]);

    /** the hook after the batch delete operation is completed */
    async function onBatchDeleted() {
        window.$message?.success($t('common.deleteSuccess'));

        checkedRowKeys.value = [];

        await getData();
    }

    /** the hook after the delete operation is completed */
    async function onDeleted() {
        window.$message?.success($t('common.deleteSuccess'));

        await getData();
    }

    return {
        drawerVisible,
        openDrawer,
        closeDrawer,
        operateType,
        handleAdd,
        editingData,
        handleEdit,
        checkedRowKeys,
        onBatchDeleted,
        onDeleted
    };
}

function isTableColumnHasKey<T>(column: any): column is NaiveUI.TableColumnWithKey<T> {
    return Boolean((column as NaiveUI.TableColumnWithKey<T>).key);
}
